package com.example.springsearchengines.service;


import java.io.*;
import java.util.ArrayList;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicLong;

public class Parser {

    private final Index index = new Index();

    //输入路径
    private final String INPUT_PATH = "D:/JAVA/文档搜索引擎/docs/api/java/util";

    private AtomicLong t1 = new AtomicLong(0);
    private AtomicLong t2 = new AtomicLong(0);

    public void load() {
        index.load();
    }

    //单线程版本
    public void run() {
        //该run方法是整个Parser类的入口
        //1.首先根据上面指定的路径进行文件的枚举，该过程需要把文件目录中的所有文件都列举出来
        ArrayList<File> fileList = new ArrayList<>();
        enumFile(INPUT_PATH,fileList);
        //2.针对上面列出的文件，打开文件，读取文件内容，并进行解析，然后构建索引
        for(File f : fileList) {
            System.out.println("开始解析: " + f.getAbsolutePath());
            pathHtml(f);
        }
        //3.把内存中构造好的索引结构进行硬件上的保存
        index.save();
    }
    //多线程版本,但如果是多线程的话就必须考虑线程安全问题
    public void runByThread() throws InterruptedException {
        long beg = System.currentTimeMillis();
        //1.首先根据上面指定的路径进行文件的枚举，该过程需要把文件目录中的所有文件都列举出来
        ArrayList<File> fileList = new ArrayList<>();
        enumFile(INPUT_PATH,fileList);
        //2.针对上面列出的文件，打开文件，读取文件内容，并进行解析，然后构建索引
        ExecutorService executorService = Executors.newFixedThreadPool(4);
        //使用countDownLatch来进行计数，count为文件的个数，一旦count减为0才可以进行硬盘上的保存
        CountDownLatch countDownLatch = new CountDownLatch(fileList.size());

        for (File f : fileList) {
            //这个线程池中只有循环一到来就会添加任务到队列中，不管任务是否已经执行成功
            //所以有可能出现循环结束了，但是所有的任务还没有执行完成就直接保存到硬盘上去了
            executorService.submit(()->{
                System.out.println("开始解析: " + f.getAbsolutePath());
                pathHtml(f);
                countDownLatch.countDown();
            });
        }
        countDownLatch.await();
        //强制结束线程池
        executorService.shutdown();
        //3.把内存中构造好的索引结构进行硬件上的保存
        index.save();
        long end = System.currentTimeMillis();
        System.out.println("索引制作时间: " + (end-beg) + "ms");
        //这里每个线程都会进相加，所以会加线程数次
        System.out.println("t1: " + t1);
        System.out.println("t2: " + t2);
    }

    private void pathHtml(File f) {
        //解析标题
        String title = pathTitle(f);
        //解析url
        String url = pathUrl(f);
        long beg = System.nanoTime();
        //解析内容
//        String content = pathContent(f);
        String content = pathContentByRegex(f);
        long mid = System.nanoTime();
        //往索引中添加文档
        index.addDoc(title,url,content);
        long end = System.nanoTime();

        t1.addAndGet(mid-beg);
        t2.addAndGet(end-mid);
    }

    private String pathUrl(File f) {
        String s1 = "https:\\\\docs.oracle.com\\javase\\8\\docs\\api";
        String s2 = f.getAbsolutePath().substring(INPUT_PATH.length());
        System.out.println(s1+s2);
        return s1 + s2;
    }

    private String pathContentByRegex(File f) {
//        1.先把整个文件读到String里面
        String content = readFile(f);
        //注意此处的顺序不能进行交换，如果进行交换，那么在进行script标签的匹配的时候，已经找不到script标签了
        //2.替换掉script标签
        content = content.replaceAll("<script.*?>(.*?)</script>"," ");
        //3.替换掉其他标签
        content = content.replaceAll("<.*?>"," ");
        //4.将多个空格替换成一个空格
        //注意此处要进行转义,单个\就表示是一个转义1字符,所以再加一个\就表示是\s（表示匹配空格）的意思了
        //+表示匹配一个以上的字符，*表示匹配0个以上的字符
        content = content.replaceAll("\\s+"," ");
        return content;
    }

    private String readFile(File f) {
        StringBuilder str = new StringBuilder();
        try(BufferedReader bufferedReader = new BufferedReader(new FileReader(f))) {
            while (true) {
                int read = bufferedReader.read();
                if(read < 0) {
                    break;
                }
                char ch = (char) read;
                if(ch == '\n' || ch == '\r') {
                    //如果为换行符或者制表符就替换为空格
                    ch = ' ';
                }
                str.append(ch);
            }
            return str.toString();
        }catch (IOException e) {
            e.printStackTrace();
        }
        return " ";
    }

    private String pathContent(File f) {
        //BufferedReader可以自动把fileReader中的一些内存预读到内存中从而减少读取IO的次数
        //进而在第一次进行启动的时候，加快速度，此处设置为1MB
        try(BufferedReader bufferedReader = new BufferedReader(new FileReader(f),1024 * 1024)) {
            boolean flag = true;
            StringBuilder content = new StringBuilder();
            while(true) {
                int ret = bufferedReader.read();
                if(ret == -1) {
                    //读取结束
                    break;
                }
                char ch = (char)ret;
                if(flag) {
                    //首先要跳过div标签
                    if(ch == '<') {
                        flag = false;
                        continue;
                    }
                    if(ch == '\n' || ch == '\r') {
                        ch = ' ';
                    }
                    content.append(ch);
                }else {
                    if(ch == '>') {
                        flag = true;
                    }
                }
            }
            return content.toString();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    private String pathTitle(File f) {
        //根据文件名字来进行获取标题
        String path = f.getName();
        //使用路径的长度减去字符串“.html“的长度来进行截取
        return f.getName().substring(0,path.length()-".html".length());
    }

    private void enumFile(String inputPath, ArrayList<File> fileList) {
        //1.获取输入文件/文件目录
        File rootPath = new File(inputPath);
        //2.列举当前文件目录下所有的文件
        File[] files = rootPath.listFiles();
        //listFiles只能获取到当前rootPath路径下的文件和目录
        //并且只能看到一级目录，要想看到二三级目录，还需要递归
        for(File file : files) {
            //如果是文件的话可以直接添加进fileList
            //但如果是目录的话，参与递归，直到不是目录
            if(file.isDirectory()) {
                enumFile(file.getAbsolutePath(),fileList);
            }else {
                //过滤除了html以外的文件
                if(file.getAbsolutePath().endsWith(".html")) {
                    fileList.add(file);
                }
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Parser parser = new Parser();
//        parser.load();
        parser.runByThread();
    }
}
